# -*- coding: utf-8 -*-
import json
import time
from django.conf import settings
import asyncio
from channels.exceptions import StopConsumer
from channels.db import database_sync_to_async
from channels.generic.websocket import WebsocketConsumer
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
from api import models


# v1版本：只能实现1v1单独对话
class ChatConsumerV1(WebsocketConsumer):
    def websocket_connect(self, message):
        """
        客户端向服务端发送websocket连接的请求时自动触发。
        """
        print(u"1 > 客户端和服务端开始建立连接")
        self.accept()

    def websocket_receive(self, message):
        """
        客户端基于websocket向服务端发送数据时，自动触发接收消息。
        """
        print(f"2 > 服务端接收客户端的消息, message is {message}")
        recv_data = message["text"]

        if recv_data == "exit":  # 服务端主动关闭websocket连接时，前端会执行对应的 onclose
            self.close()
            # raise StopConsumer()    # raise主动抛异常后，websocket_disconnect 就不在执行了，多用于`只处理服务端向客户端断开`的场景
            return

        send_data = f"服务端主动推送消息：{recv_data}"
        self.send(text_data=send_data)

    def websocket_disconnect(self, message):
        """
        客户端与服务端断开websocket连接时自动触发(不管是客户端向服务端断开还是服务端向客户端断开都会执行)
        """
        print("3 > 客户端和服务端断开连接")
        self.close()
        raise StopConsumer()


# v2版本：可以实现聊天室的功能，自定义一个全局列表
CONN_OBJ_LIST = []


class ChatConsumerV2(WebsocketConsumer):
    def websocket_connect(self, message):
        """
        客户端向服务端发送websocket连接的请求时自动触发。
        """
        print(f"{self} > 客户端和服务端开始建立连接")
        self.accept()
        # 此处的self就是那个链接，咱们把所有的连接都放到一个list中
        CONN_OBJ_LIST.append(self)
        print("当前有几个链接：", len(CONN_OBJ_LIST))

    def websocket_receive(self, message):
        """
        客户端基于websocket向服务端发送数据时，自动触发接收消息。
        """
        print(f"服务端接收客户端{self}的消息, message is {message}")
        recv_data = message["text"]
        # 服务端根据客户端发来的消息进行处理，进一步得到服务端想给客户端发送的数据
        send_data = f'{recv_data}QAQ'
        # 把某一个连接的消息发送给其他连接，for循环
        for conn in CONN_OBJ_LIST:
            conn.send(send_data)

        # send_data = f"服务端主动推送消息：{recv_data}"
        # self.send(text_data=send_data)

    def websocket_disconnect(self, message):
        """
        客户端与服务端断开websocket连接时自动触发(不管是客户端向服务端断开还是服务端向客户端断开都会执行)
        连接断开时，把连接从组里面剔除
        """
        print("3 > 客户端和服务端断开连接")
        CONN_OBJ_LIST.remove(self)
        raise StopConsumer()


# v3版本：可以实现聊天室的功能，使用channels自带的‘列表’ --->channel layers
# channel layers需要一点settings配置
# # channel layers
# CHANNEL_LAYERS = {
#     "default": {
#         "BACKEND": "channels.layers.InMemoryChannelLayer",
#     }
# }
# 群发消息，或者是websocket通信，要用群组和个人的概念，比如说，咱们几个人有个微信群组，咱们几个可以在里面聊天，
# 我想把另外一个人拉进群聊，那么我首先得知道这个人的微信号--->群号，个人号
class ChatConsumerV3(WebsocketConsumer):
    def websocket_connect(self, message):
        """
        客户端向服务端发送websocket连接的请求时自动触发。
        """
        print(f"客户端{self}和服务端开始建立连接")
        # 客户端请求连接，服务端接受该请求，self代表这个链接对象
        self.accept()
        # 通过url发送过来的‘群号’
        group = self.scope['url_route']['kwargs']['group']
        # 此处的self就是那个链接，咱们把所有的连接都放到一个list中，咱们把这个对象放到channels自己的列表中
        # self.channel_layer-->list
        # group_add-->list的append
        # (group, self.channel_name) --> (哪个组，哪个链接对象)，这么些很容易理解，但是有坑
        async_to_sync(self.channel_layer.group_add)(group, self.channel_name)

    def websocket_receive(self, message):
        """
        客户端基于websocket向服务端发送数据时，自动触发接收消息。
        """
        group = self.scope["url_route"]["kwargs"].get("group")
        print(f"服务端接收客户端{self}的消息, message is {message}")
        recv_data = message["text"]
        # 服务端根据客户端发来的消息进行处理，进一步得到服务端想给客户端发送的数据
        send_data = f'{recv_data}QAQ'
        """
        循环channels_lqayer的连接对象，挨个给他们发消息
        xx.oo反映到函数中是xx_oo
        在send里面写自定义函数的这种写法允许在发送消息之前做更多的操作
        给同组的所有成员发送消息，相当于之前的for循环发送消息，此处有坑
        """
        async_to_sync(self.channel_layer.group_send)(group, {"type": "xx.oo", "message": message})

        # send_data = f"服务端主动推送消息：{recv_data}"
        # self.send(text_data=send_data)

    def xx_oo(self, event):
        message = event["message"]
        print(message)
        text = event["message"]["text"]
        print(text)
        self.send(text)

    def websocket_disconnect(self, message):
        """
        客户端与服务端断开websocket连接时自动触发(不管是客户端向服务端断开还是服务端向客户端断开都会执行)
        当链接断开时，把用户从组里剔除，和之前全局定义list版本的逻辑一样，此处有坑
        代码看着很完美，运行一下,也没有报错，服务端也能接收到消息，但是没法发送
        不卖关子了，这三个方法都是异步方法，就是说前面都有async，不能和平常函数的用法一样，咱们得转换一下
        把异步方法转成同步的，使用async_to_sync

        """
        group = self.scope["url_route"]["kwargs"].get("group")
        async_to_sync(self.channel_layer.group_discard)(group, self.channel_name)
        print(f"客户端{self}和服务端断开连接")
        raise StopConsumer()


"""
这里有同学可能会问了，我把异步的channels方法通过async_to_sync转成同步代码，那不还是使用同步了，channels的异步还是没用上啊
解释：
在Django Channels中，通常会在异步的上下文中编写一些异步代码，比如使用async def定义的视图函数或者消费者方法。
但是，有时你可能需要在同步的上下文中调用这些异步代码，咱们的websocket_connect是同步的，group_send是异步的，
再比如在同步的中间件中调用异步的视图函数，或者在同步的代码中调用异步的数据库查询等等。

这时就需要使用到async_to_sync函数。它的作用是将异步函数转换为同步函数，让你能够在同步的上下文中调用异步函数，而不会影响整个程序的异步性能。
通过使用async_to_sync，你可以在同步代码中调用异步函数，async_to_sync会创建一个事件循环，并在其中运行异步函数，而不会阻塞整个程序。
"""

"""界面美化版本"""


class ChatConsumerV4(WebsocketConsumer):
    def websocket_connect(self, message):
        """
        客户端向服务端发送websocket连接的请求时自动触发。
        """
        self.room_name = self.scope['url_route']['kwargs']['room_name']
        self.user_name = self.scope['url_route']['kwargs']['user_name']
        print(f"客户端{self.user_name}和服务端开始建立连接")
        # 客户端请求连接，服务端接受该请求，self代表这个链接对象
        self.accept()
        # 通过url发送过来的‘群号’
        # group = self.scope['url_route']['kwargs']['group']
        async_to_sync(self.channel_layer.group_add)(self.room_name, self.channel_name)
        # 此处的self就是那个链接，咱们把所有的连接都放到一个list中，咱们把这个对象放到channels自己的列表中
        # self.channel_layer-->list
        # group_add-->list的append
        # 向当前组内发送通知
        message = {}
        if self.room_name == settings.ROOM_NAME:
            message['text'] = '{0} 已加入☆大厅☆...'.format(self.user_name)
            async_to_sync(self.channel_layer.group_send)(self.room_name, {"type": "entry_hall", "message": message})
        else:
            message['text'] = '{0} 已加入☆---{1}---☆聊天室...'.format(self.user_name, self.room_name)
            async_to_sync(self.channel_layer.group_send)(self.room_name, {"type": "entry_room", "message": message})

    def entry_hall(self,event):
        print('获取大厅用户在线状态')
        # 获取最新的用户列表， 发送用户列表的消息给前端
        users_online = list(models.User.objects.filter(is_login=1).values_list('username', flat=True))
        users_offline = list(models.User.objects.filter(is_login=0).values_list('username', flat=True))
        message = json.dumps({
            'command': 'update_users',
            'users_online': users_online,
            'users_offline': users_offline
        })

        self.send(message)
        # async_to_sync(self.channel_layer.group_send)(self.room_name, {"type": "entry_room", "message": message})

    def entry_room(self, event):
        # print(event['message'])
        # try:
        #     if json.loads(event['message']).get('command', None) == 'update_users':
        #         message = event['message']
        # except Exception as e:
        #     # message["message"]
        #     event['message']['text'] = '\n\t\t\t\t\t\t{0}\n{1}'.format(time.ctime(), event['message']['text'])
        event['message']['text'] = '\n\t\t\t\t\t\t{0}\n{1}'.format(time.ctime(), event['message']['text'])
        print(event)
        self.send(json.dumps(event))
        async_to_sync(self.channel_layer.group_send)(self.room_name, {"type": "entry_hall", "message": 'message'})

        # self.entry_hall(event)
        # Send message to WebSocket
        # self.send(text_data=json.dumps({
        #     'message': message
        # }))
    def chat_message(self, event):
        # print(event['message'])
        try:
            if json.loads(event['message']).get('command', None) == 'update_users':
                event['message']['text'] = event['message']
        except Exception as e:
            event['message']['text'] = '\n\t\t\t\t\t\t{0}\n{1}'.format(time.ctime(), json.loads(event['message']['text'])['message'])
        print(event)
        self.send(json.dumps(event))
        # Send message to WebSocket
        # self.send(text_data=json.dumps({
        #     'message': message
        # }))
    def websocket_receive(self, message):
        """
        客户端基于websocket向服务端发送数据时，自动触发接收消息。
        """
        # group = self.scope["url_route"]["kwargs"].get("group")
        # print("接收到客户端的消息")
        self.room_name = self.scope['url_route']['kwargs']['room_name']
        self.user_name = self.scope['url_route']['kwargs']['user_name']
        print(f"服务端接收客户端{self.user_name}的消息, message is {message}")
        recv_data = json.loads(message["text"])

        # 服务端根据客户端发来的消息进行处理，进一步得到服务端想给客户端发送的数据
        send_data = f'{recv_data}QAQ'
        """
        循环channels_lqayer的连接对象，挨个给他们发消息
        xx.oo反映到函数中是xx_oo
        在send里面写自定义函数的这种写法允许在发送消息之前做更多的操作
        给同组的所有成员发送消息，相当于之前的for循环发送消息，此处有坑
        """
        # if recv_data.get('command', None) == 'get_users_status':
        #     print('获取大厅用户在线状态')
        #     # 获取最新的用户列表
        #     users_online = list(models.User.objects.filter(is_login=1).values_list('username', flat=True))
        #     users_offline = list(models.User.objects.filter(is_login=0).values_list('username', flat=True))
        #     message = json.dumps({
        #         'command': 'update_users',
        #         'users_online': users_online,
        #         'users_offline': users_offline
        #     })
        #     # 发送更新用户列表的消息给前端
        #     async_to_sync(self.channel_layer.group_send)(self.room_name, {"type": "chat_message", "message": message})
        if recv_data.get('command', None) == 'logout':
            # async_to_sync(self.channel_layer.group_discard)(self.room_name, self.channel_name)
            print(f"客户端{self.user_name}和服务端断开连接")
            # self就是这个连接本身
            self.close()
            # self.channel_name.close()
            # 获取最新的用户列表
            users_online = list(models.User.objects.filter(is_login=1).values_list('username', flat=True))
            users_offline = list(models.User.objects.filter(is_login=0).values_list('username', flat=True))
            message = json.dumps({
                'command': 'update_users',
                'users_online': users_online,
                'users_offline': users_offline
            })
            # 发送更新用户列表的消息给前端
            async_to_sync(self.channel_layer.group_send)(self.room_name, {"type": "entry_hall", "message": message})
            # 用户退出登录时。
            # 1.让用户在群聊中退出
            # async_to_sync(self.channel_layer.group_discard)(self.room_name, self.channel_name)
            # 2.清除用户session信息，改变用户状态
            # 3.将最新的用户状态列表通知其他在线的用户
        else:
            async_to_sync(self.channel_layer.group_send)(self.room_name, {"type": "chat_message", "message": message})

    def websocket_disconnect(self, message):
        """
        客户端与服务端断开websocket连接时自动触发(不管是客户端向服务端断开还是服务端向客户端断开都会执行)
        当链接断开时，把用户从组里剔除，和之前全局定义list版本的逻辑一样，此处有坑
        代码看着很完美，运行一下,也没有报错，服务端也能接收到消息，但是没法发送
        不卖关子了，这三个方法都是异步方法，就是说前面都有async，不能和平常函数的用法一样，咱们得转换一下
        把异步方法转成同步的，使用async_to_sync
        """
        self.room_name = self.scope['url_route']['kwargs']['room_name']
        self.user_name = self.scope['url_route']['kwargs']['user_name']
        async_to_sync(self.channel_layer.group_discard)(self.room_name, self.channel_name)
        print(f"客户端{self.user_name}和服务端断开连接")
        # raise StopConsumer()
